package com.kakarote.ids.provider.common;

import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.kakarote.common.exception.BusinessException;
import com.kakarote.common.redis.Redis;
import com.kakarote.common.result.Result;
import com.kakarote.common.result.SystemCodeEnum;
import com.kakarote.common.servlet.ApplicationContextHolder;
import com.kakarote.ids.provider.entity.VO.TokenVO;
import feign.FeignException;
import feign.Logger;
import feign.Request;
import feign.RequestInterceptor;
import feign.Response;
import feign.RetryableException;
import feign.Retryer;
import feign.codec.Decoder;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.HttpMessageConverterCustomizer;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;
import org.springframework.cloud.openfeign.support.SpringDecoder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class FeignConfig {
    @Bean
    public Decoder feignDecoder(ObjectProvider<HttpMessageConverterCustomizer> customizers, ObjectFactory<HttpMessageConverters> messageConverters) {
        return new ResponseEntityDecoder(new FeignDecode(new SpringDecoder(messageConverters, customizers)));
    }

    @Bean
    Retryer getRetryBean() {
        return new ReLoginStrategy();
    }

    @Bean
    public Logger.Level loggerLevel() {
        return Logger.Level.FULL;
    }

    @Bean
    public RequestInterceptor interceptor() {
        return requestTemplate -> {
            Redis redis = ApplicationContextHolder.getBean(Redis.class);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                String token = request.getHeader("AUTH-TOKEN");
                if (StrUtil.isNotEmpty(token) && redis.exists(token + Const.TOKEN_CACHE_NAME)) {
                    TokenVO data = redis.get(token + Const.TOKEN_CACHE_NAME);
                    requestTemplate.header("Authorization", new String[]{"Bearer " + data.getAccessToken()});
                }
            } else {
                OAuthProperties oAuthProperties = ApplicationContextHolder.getBean(OAuthProperties.class);
            }
        };
    }

    public static class FeignDecode implements Decoder {
        private final SpringDecoder decoder;

        FeignDecode(SpringDecoder decoder) {
            this.decoder = decoder;
        }

        public Object decode(Response response, Type type) throws FeignException, IOException {
            Object data = this.decoder.decode(response, type);
            if (data instanceof Result) {
                if (((Result) data).getCode().equals(Integer.valueOf(SystemCodeEnum.SYSTEM_NOT_LOGIN.getCode())))
                    throw new RetryableException(SystemCodeEnum.SYSTEM_NOT_LOGIN.getCode(), SystemCodeEnum.SYSTEM_NOT_LOGIN.getMsg(), Request.HttpMethod.POST, new Date(), response.request());
                if (!((Result) data).hasSuccess())
                    throw new BusinessException(((Result) data).getCode(), ((Result) data).getMsg());
            }
            return data;
        }
    }

    public static class ReLoginStrategy implements Retryer {
        private boolean initToken = true;

        public void continueOrPropagate(RetryableException e) {
            if (SystemCodeEnum.SYSTEM_NOT_LOGIN.getCode() == e.status() && this.initToken) {
                Redis redis = ApplicationContextHolder.getBean(Redis.class);
                Collection<String> collection = e.request().headers().get("AUTH-TOKEN");
                if (collection == null || collection.isEmpty())
                    throw new BusinessException(SystemCodeEnum.SYSTEM_NOT_LOGIN);
                String token = collection.iterator().next() + Const.TOKEN_CACHE_NAME;
                if (!redis.exists(token))
                    throw new BusinessException(SystemCodeEnum.SYSTEM_NOT_LOGIN);
                TokenVO tokenVO = redis.get(token);
                OAuthProperties properties = ApplicationContextHolder.getBean(OAuthProperties.class);
                HttpRequest request = HttpUtil.createPost(Const.GET_TOKEN_URI);
                JSONObject object = new JSONObject();
                object.put("clientId", properties.getClientId());
                object.put("clientSecret", properties.getClientSecret());
                object.put("redirectUri", properties.getRedirectUri());
                object.put("scope", "user");
                object.put("grantType", "refresh_token");
                object.put("refreshToken", tokenVO.getRefreshToken());
                try {
                    request.body(object.toJSONString());
                    HttpResponse execute = request.execute();
                    JSONObject body = JSON.parseObject(execute.body());
                    Result<TokenVO> result = Result.of(body.getInteger("code").intValue(), body.getString("msg"));
                    if (result.hasSuccess()) {
                        TokenVO data = (TokenVO) body.getObject("data", TokenVO.class);
                        redis.setex(token, Integer.valueOf(redis.ttl(token).intValue()), data);
                    }
                } catch (Exception exception) {
                }
                this.initToken = false;
            }
        }

        public Retryer clone() {
            return new ReLoginStrategy();
        }
    }
}
